/* Show average execution time when schedule various number of threads */

#include <stdio.h>
#include "tests/threads/tests.h"
#include "threads/init.h"
#include "threads/malloc.h"
#include "threads/synch.h"
#include "threads/thread.h"
#include "devices/timer.h"

static thread_func ctime_schedule_thread;
static thread_func priority_check_thread;
static int64_t wake_time;
static struct semaphore wait_sema;
static int thread_num;
static struct lock tnum_lock;

void
test_ctime_schedule (void) 
{
  int i;
  
  /* This test does not work with the MLFQS. */
  ASSERT (!thread_mlfqs);

  lock_init (&tnum_lock);
  sema_init (&wait_sema, 0);

  int t_num;

  /* Start first test */

  wake_time = timer_ticks () + 5 * TIMER_FREQ;

  t_num = 5;
  thread_num = t_num;

  for(i = 0; i < t_num; i++)
  {
    int priority = PRI_DEFAULT + ( i + 2 ) % 5 - 1;
    char name[16];
    snprintf (name, sizeof name, "priority %d", priority);
    thread_create (name, priority, priority_check_thread, NULL);
  }

  while(thread_num > 0)
    barrier();
  
  for(i = 0; i < t_num; i++)
    sema_down (&wait_sema);


  /* Start second test */

  for(t_num = 10; t_num <= 100; t_num = t_num + 10)
  {
    
    wake_time = timer_ticks () + 5 * TIMER_FREQ;
    
    thread_num = t_num;
    
    for (i = 0; i < t_num; i++) 
      {
        int priority = ( i % 62 ) + 2;
        char name[16];
        snprintf (name, sizeof name, "thread %d", i);
        thread_create (name, priority, ctime_schedule_thread, NULL);
      }

    
    thread_set_priority (PRI_MIN);

    /* Wait until all threads end. */
    while(thread_num > 0){
      barrier();
    }
    
    msg("%d Threads Avg(Exec. time): %d.", t_num, (timer_elapsed (wake_time)*10)/t_num);

    for (i = 0; i < t_num; i++)
      sema_down (&wait_sema);    
  }

  
  /* Print Thread's approximate execution time */

  int64_t start_time = timer_ticks ();

  /* Busy-wait */
  int64_t loop_cnt = 5000;
  while (loop_cnt-- > 0)
    barrier();
  
  msg("Busy-wait: %d.", timer_elapsed (start_time));

}

/* Thread function for first test */
static void
priority_check_thread (void *aux UNUSED)
{
  /* Busy-wait until the current time changes. */
  int64_t start_time = timer_ticks();
  while (timer_elapsed (start_time) == 0)
    barrier();

  timer_sleep (wake_time - timer_ticks ());

  msg ("Thread %s woke up at %d.", thread_name (), timer_ticks ());

  lock_acquire (&tnum_lock);
  thread_num --;
  lock_release (&tnum_lock);
  
  sema_up(&wait_sema);
}  

/* Thread function for second test */
static void
ctime_schedule_thread (void *aux UNUSED) 
{
  /* Busy-wait until the current time changes. */
  int64_t start_time = timer_ticks ();
  while (timer_elapsed (start_time) == 0)
    barrier();

  timer_sleep (wake_time - timer_ticks ());

  /* Busy-wait */
  int64_t loop_cnt = 5000;

  while(loop_cnt -- > 0)
    barrier();

  lock_acquire (&tnum_lock);
  thread_num --;
  lock_release (&tnum_lock);

  sema_up (&wait_sema);
}
